package com.soonoh.simplecube.cube;

import android.opengl.Matrix;

public class Matrix4f {
	public static final int XX = 0;
	public static final int XY = 1;
	public static final int XZ = 2;
	public static final int XW = 3;
	public static final int YX = 4;
	public static final int YY = 5;
	public static final int YZ = 6;
	public static final int YW = 7;
	public static final int ZX = 8;
	public static final int ZY = 9;
	public static final int ZZ = 10;
	public static final int ZW = 11;
	public static final int TX = 12;
	public static final int TY = 13;
	public static final int TZ = 14;
	public static final int TW = 15;

	public float[] m;

	public Matrix4f() {
		this(new float[] {
			1.0f, 0.0f, 0.0f, 0.0f,
			0.0f, 1.0f, 0.0f, 0.0f,
			0.0f, 0.0f, 1.0f, 0.0f,
			0.0f, 0.0f, 0.0f, 1.0f
		});
	}
	
	public Matrix4f(float[] m) {
		this.m = m;
	}
	
	public float XX() { return m[XX]; };
	public float XY() { return m[XY]; };
	public float XZ() { return m[XZ]; };
	public float XW() { return m[XW]; };
	public float YX() { return m[YX]; };
	public float YY() { return m[YY]; };
	public float YZ() { return m[YZ]; };
	public float YW() { return m[YW]; };
	public float ZX() { return m[ZX]; };
	public float ZY() { return m[ZY]; };
	public float ZZ() { return m[ZZ]; };
	public float ZW() { return m[ZW]; };
	public float TX() { return m[TX]; };
	public float TY() { return m[TY]; };
	public float TZ() { return m[TZ]; };
	public float TW() { return m[TW]; };

	public void copyTo(Matrix4f clone) {
		for (int i = 0; i < m.length; ++i) {
			clone.m[i] = this.m[i];
		}
	}

	public void multiply(Matrix4f operand) {
		Matrix4f result = new Matrix4f();
        result.m[Matrix4f.XX] = (m[XX] * operand.XX()) + (m[YX] * operand.XY()) + (m[ZX] * operand.XZ()) + (m[TX] * operand.XW());
        result.m[Matrix4f.YX] = (m[XX] * operand.YX()) + (m[YX] * operand.YY()) + (m[ZX] * operand.YZ()) + (m[TX] * operand.YW());
        result.m[Matrix4f.ZX] = (m[XX] * operand.ZX()) + (m[YX] * operand.ZY()) + (m[ZX] * operand.ZZ()) + (m[TX] * operand.ZW());
        result.m[Matrix4f.TX] = (m[XX] * operand.TX()) + (m[YX] * operand.TY()) + (m[ZX] * operand.TZ()) + (m[TX] * operand.TW());

        result.m[Matrix4f.XY] = (m[XY] * operand.XX()) + (m[YY] * operand.XY()) + (m[ZY] * operand.XZ()) + (m[TY] * operand.XW());
        result.m[Matrix4f.YY] = (m[XY] * operand.YX()) + (m[YY] * operand.YY()) + (m[ZY] * operand.YZ()) + (m[TY] * operand.YW());
        result.m[Matrix4f.ZY] = (m[XY] * operand.ZX()) + (m[YY] * operand.ZY()) + (m[ZY] * operand.ZZ()) + (m[TY] * operand.ZW());
        result.m[Matrix4f.TY] = (m[XY] * operand.TX()) + (m[YY] * operand.TY()) + (m[ZY] * operand.TZ()) + (m[TY] * operand.TW());

        result.m[Matrix4f.XZ] = (m[XZ] * operand.XX()) + (m[YZ] * operand.XY()) + (m[ZZ] * operand.XZ()) + (m[TZ] * operand.XW());
        result.m[Matrix4f.YZ] = (m[XZ] * operand.YX()) + (m[YZ] * operand.YY()) + (m[ZZ] * operand.YZ()) + (m[TZ] * operand.YW());
        result.m[Matrix4f.ZZ] = (m[XZ] * operand.ZX()) + (m[YZ] * operand.ZY()) + (m[ZZ] * operand.ZZ()) + (m[TZ] * operand.ZW());
        result.m[Matrix4f.TZ] = (m[XZ] * operand.TX()) + (m[YZ] * operand.TY()) + (m[ZZ] * operand.TZ()) + (m[TZ] * operand.TW());

        result.m[Matrix4f.XW] = (m[XW] * operand.XX()) + (m[YW] * operand.XY()) + (m[ZW] * operand.XZ()) + (m[TW] * operand.XW());
        result.m[Matrix4f.YW] = (m[XW] * operand.YX()) + (m[YW] * operand.YY()) + (m[ZW] * operand.YZ()) + (m[TW] * operand.YW());
        result.m[Matrix4f.ZW] = (m[XW] * operand.ZX()) + (m[YW] * operand.ZY()) + (m[ZW] * operand.ZZ()) + (m[TW] * operand.ZW());
        result.m[Matrix4f.TW] = (m[XW] * operand.TX()) + (m[YW] * operand.TY()) + (m[ZW] * operand.TZ()) + (m[TW] * operand.TW());
		result.copyTo(this);
	}
	
	public Matrix4f invert() {
		float[] mInv = new float[16];
		Matrix.invertM(mInv, 0, m, 0);
		return new Matrix4f(mInv);
	}
	public float[] multiply(float[] operand, int unitSize) {
		float[] result = new float[operand.length];
//		for (int i = 0; i < operand.length; ++i) {
//			result[i] = 0.0f;
//			int k = i%unitSize;
//			int n = i/unitSize;
//			for (int j = 0; j < unitSize; ++j) {
//				result[i] += (operand[n*unitSize+j]*m[k*4+j]);
//			}
//			if (unitSize == 3) {
//				result[i] += m[k*4+3];
//			}
//		}
        result[0] = (operand[0]*m[XX]) + (operand[1]*m[XY]) + (operand[2]*m[XZ]) + (operand[3]*m[XW]);
        result[1] = (operand[0]*m[YX]) + (operand[1]*m[YY]) + (operand[2]*m[YZ]) + (operand[3]*m[YW]);
        result[2] = (operand[0]*m[ZX]) + (operand[1]*m[ZY]) + (operand[2]*m[ZZ]) + (operand[3]*m[ZW]);
        result[3] = (operand[0]*m[TX]) + (operand[1]*m[TY]) + (operand[2]*m[TZ]) + (operand[3]*m[TW]);
        return result;
	}

	public static Matrix4f getGluLookAtMatrix(float eyeX, float eyeY, float eyeZ, float centreX, float centreY, float centreZ, float upX, float upY, float upZ) {
		Vector3f forward = new Vector3f();
		forward.X = centreX - eyeX;
		forward.Y = centreY - eyeY;
		forward.Z = centreZ - eyeZ;
		Vector3f up = new Vector3f();
		up.X = upX; up.Y = upY; up.Z = upZ;
		forward.normalise();
		Vector3f side = forward.getCrossProduct(up);
		side.normalise();
		
		up = side.getCrossProduct(forward);
		Matrix4f result = new Matrix4f();
		result.m[XX] = side.X; result.m[XY] = side.Y; result.m[XZ] = side.Z;
		result.m[YX] = up.X; result.m[YY] = up.Y; result.m[YZ] = up.Z;
		result.m[ZX] = -forward.X; result.m[ZY] = -forward.Y; result.m[ZZ] = -forward.Z;
//		result.m[XX] = side.X; result.m[YX] = side.Y; result.m[YZ] = side.Z;
//		result.m[XY] = up.X; result.m[YY] = up.Y; result.m[ZY] = up.Z;
//		result.m[XZ] = -forward.X; result.m[YZ] = -forward.Y; result.m[ZZ] = -forward.Z;
		Matrix4f trans = new Matrix4f();
		trans.m[TX] = -eyeX; trans.m[TY] = -eyeY; trans.m[TZ] = -eyeZ;
//		result.multiply(trans);
		trans.multiply(result);
		trans.copyTo(result);
		return result;
	}
}
